﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

using Utilities = OpenTK.Platform.Utilities;


namespace ArtemisTest
{

    /*
                Model {
                    Mesh
                    Material
                }
                Mesh {
                    Texture
                    Shader
                }
                Mesh {
                    IBO
                    VBO
                }
     */
    public class ModelDrawingSystem : CSystem
    {
        //Data for models and stuff
        List<ObjModel> _models;
        GameWindow _window;

        int _vertexShaderHandle;
        int _fragmentShaderHandle;
        int _shaderProgramHandle;
        int _modelviewMatrixHandle;
        int _projectionMatrixHandle;
        int _vaoHandle;
        int _positionVboHandle;
        int _normalVboHandle;
        int _textureVboHandle;
        int _eboHandle;

        Vector3[] _normalVboData = new Vector3[0];
        Vector3[] _textureVboData = new Vector3[0];
        Vector3[] _positionVboData = new Vector3[0];
        uint[]    _indicesVboData = new uint[0];

        Matrix4 _projectionMatrix;
        Matrix4 _modelviewMatrix;

        void CreateShaders()
        {
            _vertexShaderHandle = GL.CreateShader(ShaderType.VertexShader);
            _fragmentShaderHandle = GL.CreateShader(ShaderType.FragmentShader);

            GL.ShaderSource(_vertexShaderHandle, ScriptReader.GetString("shaders\\vertex.txt"));
            GL.ShaderSource(_fragmentShaderHandle, ScriptReader.GetString("shaders\\fragment.txt"));

            GL.CompileShader(_vertexShaderHandle);
            GL.CompileShader(_fragmentShaderHandle);

            Debug.WriteLine(GL.GetShaderInfoLog(_vertexShaderHandle));
            Debug.WriteLine(GL.GetShaderInfoLog(_fragmentShaderHandle));

            _shaderProgramHandle = GL.CreateProgram();

            GL.AttachShader(_shaderProgramHandle, _vertexShaderHandle);
            GL.AttachShader(_shaderProgramHandle, _fragmentShaderHandle);

            GL.LinkProgram(_shaderProgramHandle);

            Debug.WriteLine(GL.GetProgramInfoLog(_shaderProgramHandle));

            GL.UseProgram(_shaderProgramHandle);

            _projectionMatrixHandle = 
                GL.GetUniformLocation(_shaderProgramHandle, "projection_matrix");

            _modelviewMatrixHandle = 
                GL.GetUniformLocation(_shaderProgramHandle, "modelview_matrix");

            float aspectRatio = _window.ClientSize.Width / (float)(_window.ClientSize.Height);

            Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, aspectRatio, 1, 100, out _projectionMatrix);
            _modelviewMatrix = Matrix4.LookAt(new Vector3(-45, 45, -30), new Vector3(0, 0, 0), new Vector3(0, 1, 0));

            GL.UniformMatrix4(_projectionMatrixHandle, false, ref _projectionMatrix);
            GL.UniformMatrix4(_modelviewMatrixHandle, false, ref _modelviewMatrix);
        }

        void CreateVbos()
        {
            GL.GenBuffers(1, out _positionVboHandle);   //Create the handle
            GL.GenBuffers(1, out _normalVboHandle);
            GL.GenBuffers(1, out _textureVboHandle);
            GL.GenBuffers(1, out _eboHandle);
        }


        void CreateVAO()
        {
            GL.GenVertexArrays(1, out _vaoHandle);  //Create handle
            GL.BindVertexArray(_vaoHandle);         

            GL.EnableVertexAttribArray(0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, _positionVboHandle);
            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, 
                true, Vector3.SizeInBytes, 0);
            GL.BindAttribLocation(_shaderProgramHandle, 0, "in_position");

            GL.EnableVertexAttribArray(1);
            GL.BindBuffer(BufferTarget.ArrayBuffer, _normalVboHandle);
            GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float,
                true, Vector3.SizeInBytes, 0);
            GL.BindAttribLocation(_shaderProgramHandle, 1, "in_normal");

            GL.EnableVertexAttribArray(2);
            GL.BindBuffer(BufferTarget.ArrayBuffer, _textureVboHandle);
            GL.VertexAttribPointer(2, 3, VertexAttribPointerType.Float,
                true, Vector3.SizeInBytes, 0);
            GL.BindAttribLocation(_shaderProgramHandle, 1, "in_texture");

            GL.BindBuffer(BufferTarget.ElementArrayBuffer, _eboHandle);

            GL.BindVertexArray(0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
        }

        public void Init(TextureSystem ts)
        {
            CreateShaders();
            CreateVbos();
            CreateVAO();

            LoadObjects(ScriptReader.GetData("model\\paths.txt"),ts);

            GL.Enable(EnableCap.DepthTest);
            GL.ClearColor(Color.CornflowerBlue);
        }

        /// <summary>OnUpdateFrame plz</summary>
        public override void PreUpdate(double dt)
        {
            Matrix4 rotation = Matrix4.CreateRotationY((float)dt);
            Matrix4.Mult(ref rotation, ref _modelviewMatrix, out _modelviewMatrix);
            GL.UniformMatrix4(_modelviewMatrixHandle, false, ref _modelviewMatrix);
        }

        Random rand = new Random();

        public void DrawMesh(MeshId m, TextureSystem ts)
        {

            
            GL.DrawElements(BeginMode.Quads, m.indCount,
            DrawElementsType.UnsignedInt,m.indPos * sizeof(uint));
            //GL.DrawElementsInstanced(BeginMode.Quads, m.indCount, DrawElementsType.UnsignedInt, ref _indicesVboData[m.indPos], m.vertCount/4);
        }

        public void Update(double dt, TextureSystem ts)
        {
            GL.Viewport(0, 0, 800, 600);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            //gl bind texture

            

            GL.Uniform1(GL.GetUniformLocation(_shaderProgramHandle, "img"), 0);
            GL.BindVertexArray(_vaoHandle);

            ts.Bind(_models[5]._material.map_Kd_DiffuseHandle);
            DrawMesh(_meshData[5],ts);

            _window.SwapBuffers();
        }

        public override void PostUpdate(double dt)
        {
        }

        public struct MeshId
        {
            public int vertPos;
            public int vertCount;
            public int indCount;
            public int indPos;
        }

        List<MeshId> _meshData = new List<MeshId>();
        //List<ObjModel> _objects = new List<ObjModel>();

        public MeshId LoadObject(string pathFile, TextureSystem ts)
        {
            ObjModel mod = ModelReader.LoadModel(pathFile,ts);
            _models.Add(mod);

            MeshId mid = new MeshId() 
            {       
                    vertPos = _positionVboData.Length,
                    indCount = mod._posInd.Count,
                    indPos = _indicesVboData.Length, 
                    vertCount = mod._verts.Count };

            //_positionVboData = mod._verts.ToArray(); //Set position data

            //Add position vertices.
            List<Vector3> posData = _positionVboData.ToList();
            posData.AddRange(mod._verts);
            _positionVboData = posData.ToArray();

            GL.BindBuffer(BufferTarget.ArrayBuffer, _positionVboHandle);//Set current buffer
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,        //Set Data
                new IntPtr(_positionVboData.Length * Vector3.SizeInBytes),
                _positionVboData, BufferUsageHint.StaticDraw);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);             //Release buffer

            //Add normals
            List<Vector3> normData = _normalVboData.ToList();
            normData.AddRange(mod._norms);
            _normalVboData = normData.ToArray();

            GL.BindBuffer(BufferTarget.ArrayBuffer, _normalVboHandle);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,
                new IntPtr(_normalVboData.Length * Vector3.SizeInBytes),
                _normalVboData, BufferUsageHint.StaticDraw);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

            //Add textureCoords
            List<Vector3> texData = _textureVboData.ToList();
            texData.AddRange(mod._texts);
            _textureVboData = texData.ToArray();

            GL.BindBuffer(BufferTarget.ArrayBuffer, _textureVboHandle);
            GL.BufferData<Vector3>(BufferTarget.ArrayBuffer,
                new IntPtr(_textureVboData.Length * Vector3.SizeInBytes),
                _textureVboData, BufferUsageHint.StaticDraw);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);

            //Add indices
            List<uint> indData = _indicesVboData.ToList();
            List<uint> newIndData = mod._posInd;

            Console.WriteLine("Indices : ");

            for (int i = 0; i < newIndData.Count; i++)
            {
                newIndData[i] += (uint)mid.vertPos;
                //newIndData[i+1] += (uint)mid.;
                //newIndData[i+2] += (uint)mid.vertPos;
            }

            uint vp = (uint)mid.vertPos;

            

            for (int i = 0; i < newIndData.Count; i += 4)
            {
                Vector3 va = _positionVboData[newIndData[i]];
                Vector3 vb = _positionVboData[newIndData[i+1]];
                Vector3 vc = _positionVboData[newIndData[i+2]];
                Vector3 vd = _positionVboData[newIndData[i+3]];

                Console.WriteLine(
                          newIndData[i] + ":" + (newIndData[i] - vp) + " <" + va.X + "," + va.Y + "," + va.Z + "> " +
                    " " + newIndData[i + 1] + ":" + (newIndData[i + 1] - vp) + " <" + vb.X + "," + vb.Y + "," + vb.Z + "> " +
                    " " + newIndData[i + 2] + ":" + (newIndData[i + 2] - vp) + " <" + vc.X + "," + vc.Y + "," + vc.Z + "> " +
                    " " + newIndData[i + 3] + ":" + (newIndData[i + 3] - vp) + " <" + vd.X + "," + vd.Y + "," + vd.Z + "> ");
            }

            indData.AddRange(newIndData);//(mod._posInd);
            _indicesVboData = indData.ToArray();

            GL.BindBuffer(BufferTarget.ElementArrayBuffer, _eboHandle);
            GL.BufferData(BufferTarget.ElementArrayBuffer,
                new IntPtr(_indicesVboData.Length * sizeof(uint)),
                _indicesVboData, BufferUsageHint.StaticDraw);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);

            return mid;
        }

        public void LoadObjects(List<string[]> pathsFile, TextureSystem ts)
        {
            _models = new List<ObjModel>();
            int modelN = pathsFile.Count;
            for (int i = 0; i < modelN; i++)
            {
                _meshData.Add(LoadObject(pathsFile[i][0],ts));
            }
            
        }

        public ModelDrawingSystem(EntitySystem es, ScriptSystem ss, GameWindow window) : base(es, ss)
        {
            _window = window;
        }

        public override void Init()
        {
            
        }

        public override void Update(double dt)
        {

        }
    }

    public class TextureSystem
    {
        List<int> _textureHandles;
        //GameWindow _window;

        public TextureSystem() { _textureHandles = new List<int>(); }

        public void Bind(int texHandle)
        {
            GL.BindTexture(TextureTarget.Texture2D, texHandle);// _textureHandles[texHandle]);
        }

        public void LoadBitmaps(List<string[]> pathsFile)
        {
            _textureHandles = new List<int>();
            int pathN = pathsFile.Count;
            for (int i = 0; i < pathN; i++)
            {
                _textureHandles.Add(LoadBitmapToOpenGl(pathsFile[i][0]));
            }
        }

        public int LoadBitmapToOpenGl(string filename)
        {
            if (String.IsNullOrEmpty(filename))
                throw new ArgumentException(filename);

            int id = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, id);

            Bitmap bmp = new Bitmap(filename);

            BitmapData bmp_data = bmp.LockBits(
                new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.ReadOnly,
                System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
                bmp_data.Width, bmp_data.Height, 0,
                OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bmp_data.Scan0);

            bmp.UnlockBits(bmp_data);

            // We haven't uploaded mipmaps, so disable mipmapping (otherwise the texture will not appear).
            // On newer video cards, we can use GL.GenerateMipmaps() or GL.Ext.GenerateMipmaps() to create
            // mipmaps automatically. In that case, use TextureMinFilter.LinearMipmapLinear to enable them.
            GL.TexParameter(TextureTarget.Texture2D,
                TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);

            GL.TexParameter(TextureTarget.Texture2D,
                TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);

            return id;
        }
    }

    //public class DrawingSystem : CSystem
    //{
    //    //List<int> _textureHandles;
    //    //GameWindow _gameWindow;

    //    TextureSystem _textureSystem;

    //    public override void Init()
    //    {
            
    //        _textureSystem.LoadBitmaps(ScriptReader.GetData("img\\paths.txt"));
            
    //        GL.Enable(EnableCap.Texture2D);
    //    }

    //    public override void PreUpdate(double dt)
    //    {
    //        //GL.Disable(EnableCap.Lighting);
    //        //GL.Clear(ClearBufferMask.ColorBufferBit);
    //    }

        

    //    public override void Update(double dt)
    //    {

    //        //return;
    //        Dictionary<long,Component> pics = 
    //            EntitySystem.Components.GetAllComponentsOfType(ComponentType.img);

    //        Dictionary<long,Component> posArray = 
    //            EntitySystem.Components.GetAllComponentsOfType(ComponentType.position);

    //        Dictionary<long, Component> boundsArray =
    //            EntitySystem.Components.GetAllComponentsOfType(ComponentType.bounds);

    //        foreach (KeyValuePair<long, Component> entry in pics)
    //        {
    //            Position pos = (Position)posArray[entry.Key];
    //            Img im= (Img)entry.Value;
    //            Bounds bounds = (Bounds)boundsArray[entry.Key];

    //            GL.BindTexture(TextureTarget.Texture2D, _textureHandles[im.TextureId]);
    //            GL.Begin(BeginMode.Quads);

    //            if (Exists(pos))
    //            {
    //                GL.Color4(im.Color);
    //                GL.TexCoord2(0, 1);
    //                GL.Vertex2(pos.X, pos.Y);

    //                //Vertex 2         
    //                GL.Color4((im.Color));
    //                GL.TexCoord2(0, 0);
    //                GL.Vertex2(pos.X, pos.Y + bounds.Height);

    //                //Vertex 3
    //                GL.Color4((im.Color));
    //                GL.TexCoord2(1, 0);
    //                GL.Vertex2(pos.X + bounds.Width, pos.Y + bounds.Height);

    //                //Vertex 4
    //                GL.Color4((im.Color));
    //                GL.TexCoord2(1, 1);
    //                GL.Vertex2(pos.X + bounds.Width, pos.Y);

    //                GL.End();
    //            }

    //        } 
    //    }


    //    public override void PostUpdate(double dt)
    //    {

    //        #region postUpdate

            
    //        _gameWindow.SwapBuffers();

    //        #endregion
    //    }

    //    public DrawingSystem(EntitySystem es, GameWindow gw,
    //        TextureSystem ts, ScriptSystem ss) : base(es,ss)
    //    {
    //        _gameWindow = gw;
    //        _textureSystem = ts;
    //    }
    //}
}
